﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Input;
using System.IO;
using System.IO.IsolatedStorage;

namespace DungDungShit
{
    class Dungeon
    {
        public static int unitMazeWidth = 9;
        public static int unitMazeHeight = 9;
        public static int openRate = 10;
        public static int edgeOpenRate = 20;

        Dictionary<IntVector2, Room> roomList;
        List<RoomEntrance> path;

        IntVector2 unitIndex;
        ScreenManager screenManager;

        //Random rNum = new Random();

        public Dungeon(Dictionary<IntVector2, Room> list, ScreenManager screenManager)
        {
            roomList = list;
            this.screenManager = screenManager;
        }

        public Dictionary<IntVector2, Room> GetGeneratedMap(Rectangle rect, IntVector2 unitIndex, bool isBase = false)
        {
            this.unitIndex = unitIndex;
            GenerateMap(rect.Width, rect.Height, rect.Left, rect.Top, isBase);
            return roomList;
        }

        private Room Side(Room room, int direction, Dictionary<IntVector2, Room> roomList)
        {
            if (direction == RoomEntrance.left)
                return Left(room, roomList);
            else if (direction == RoomEntrance.right)
                return Right(room, roomList);
            else if (direction == RoomEntrance.up)
                return Up(room, roomList);
            else if (direction == RoomEntrance.down)
                return Down(room, roomList);
            else
                return null;
        }
        private Room Side(Room room, int direction)
        {
            if (direction == RoomEntrance.left)
                return Left(room, roomList);
            else if (direction == RoomEntrance.right)
                return Right(room, roomList);
            else if (direction == RoomEntrance.up)
                return Up(room, roomList);
            else if (direction == RoomEntrance.down)
                return Down(room, roomList);
            else
                return null;
        }
        private Room Left(Room room, Dictionary<IntVector2, Room> roomList)
        {
            IntVector2 pos = new IntVector2(room.position.X, room.position.Y);
            pos.X--;
            if (roomList.ContainsKey(pos))
                return roomList[pos];
            else
                return null;
        }
        private Room Right(Room room, Dictionary<IntVector2, Room> roomList)
        {
            IntVector2 pos = new IntVector2(room.position.X, room.position.Y);
            pos.X++;
            if (roomList.ContainsKey(pos))
                return roomList[pos];
            else
                return null;
        }
        private Room Up(Room room, Dictionary<IntVector2, Room> roomList)
        {
            IntVector2 pos = new IntVector2(room.position.X, room.position.Y);
            pos.Y--;
            if (roomList.ContainsKey(pos))
                return roomList[pos];
            else
                return null;
        }
        private Room Down(Room room, Dictionary<IntVector2, Room> roomList)
        {
            IntVector2 pos = new IntVector2(room.position.X, room.position.Y);
            pos.Y++;
            if (roomList.ContainsKey(pos))
                return roomList[pos];
            else
                return null;
        }

        private List<RoomEntrance> CanEnter(RoomEntrance room)
        {
            List<RoomEntrance> list = new List<RoomEntrance>();
            if(Side(room.room, room.direction) != null)
                list.Add(new RoomEntrance(Side(room.room, room.direction), RoomEntrance.Opposite(room.direction)));
            for (int i = 0; i < 3; i++)
            {
                if (room.direction == i)
                    continue;
                if (room.room.link[room.direction, i])
                    list.Add(new RoomEntrance(room.room, i));
            }
            return list;
        }
        private bool MazeCheck(RoomEntrance start, RoomEntrance end)
        {
            path = new List<RoomEntrance>();
            return Checking(start, end);
        }
        private bool EnterAll(List<RoomEntrance> enterList, RoomEntrance end)
        {
            if (enterList.Count == 0)
            {
                return false;
            }
            else if (path.Contains(enterList[0]))
            {
                path.Add(enterList[0]);
                enterList.RemoveAt(0);
                return EnterAll(enterList, end);
            }
            else if (Checking(enterList[0], end))
            {
                return true;
            }
            else
            {
                path.Add(enterList[0]);
                return EnterAll(CanEnter(enterList[0]), end);
            }
        }
        private bool Checking(RoomEntrance current, RoomEntrance end)
        {
            if (current == end)
            {
                return true;
            }
            else
            {
                return EnterAll(CanEnter(current), end);
            }
        }

        private void Open(Room room, int direction, Dictionary<IntVector2, Room> maze)
        {
            room.open[direction] = true;
            Room temp = Side(room, direction, maze);
            if (temp != null)
                temp.open[RoomEntrance.Opposite(direction)] = true;
        }
        private void Open(RoomEntrance room, int direction)
        {
            room.room.link[room.direction, direction] = true;
            for (int i = 0; i < 4; i++)
            {
                if (room.room.link[direction, i])
                {
                    room.room.link[room.direction, i] = true;
                    room.room.link[i, room.direction] = true;
                }
            }
            room.room.link[direction, room.direction] = true;
            for (int i = 0; i < 4; i++)
            {
                if (room.room.link[room.direction, i])
                {
                    room.room.link[direction, i] = true;
                    room.room.link[i, direction] = true;
                }
            }
            if (room.room.link[0, 1] && room.room.link[2, 3])
            {
                for (int i = 0; i < 4; i++)
                {
                    for (int j = 0; j < 4; j++)
                    {
                        room.room.link[i, j] = true;
                    }
                }
            }
        }
        private void OpenSingle(Room room, int direction, Dictionary<IntVector2, Room> maze)
        {
            room.open[direction] = true;
        }
        private void OpenAll(Room room, Dictionary<IntVector2, Room> maze)
        {
            for (int i = 0; i < 3; i++)
            {
                Open(room, i, maze);
                for (int j = 0; j < 3; j++)
                {
                    Open(new RoomEntrance(room, i), j);
                }
            }
        }

        private Dictionary<IntVector2, Room> InitMaze(int m, int n)
        {
            Dictionary<IntVector2, Room> list = new Dictionary<IntVector2, Room>();
            for (int i = 0; i < m; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    list.Add(new IntVector2(i, j), new Room(new IntVector2(i, j), unitIndex));
                }
            }
            return list;
        }
        private bool IsVisited(RoomEntrance room)
        {
            /*if (room.room == null)
                return true;*/
            if (path.Contains(room))
                return true;
            if (room.room.position.X < 0 || room.room.position.Y < 0 || room.room.position.X > unitMazeWidth || room.room.position.Y > unitMazeHeight)
            {
                return true;
            }
            if (room.direction == RoomEntrance.up && room.room.position.Y < 1)
                return true;
            if (room.direction == RoomEntrance.down && room.room.position.Y > unitMazeHeight - 2)
                return true;
            if (room.direction == RoomEntrance.left && room.room.position.X < 1)
                return true;
            if (room.direction == RoomEntrance.right && room.room.position.X > unitMazeWidth - 2)
                return true;
            return false;
        }
        private bool DeadEnd(RoomEntrance room)
        {
            if (!(room.direction == 0) && !(IsVisited(new RoomEntrance(room.room, 0))))
            {
                if (room.direction == 2 || room.direction == 3)
                    return false;
                if (!room.room.link[2, 3])
                    return false;
            }
            if (!(room.direction == 1) && !(IsVisited(new RoomEntrance(room.room, 1))))
            {
                if (room.direction == 2 || room.direction == 3)
                    return false;
                if (!room.room.link[2, 3])
                    return false;
            }
            if (!(room.direction == 2) && !(IsVisited(new RoomEntrance(room.room, 2))))
            {
                if (room.direction == 0 || room.direction == 1)
                    return false;
                if (!room.room.link[0, 1])
                    return false;
            }
            if (!(room.direction == 3) && !(IsVisited(new RoomEntrance(room.room, 3))))
            {
                if (room.direction == 0 || room.direction == 1)
                    return false;
                if (!room.room.link[0, 1])
                    return false;
            }
            return true;
        }
        private int NextRoom(RoomEntrance room)
        {
            List<int> list = new List<int>();
            if (!(room.direction == 0) && !(IsVisited(new RoomEntrance(room.room, 0))))
                list.Add(0);
            if (!(room.direction == 1) && !(IsVisited(new RoomEntrance(room.room, 1))))
                list.Add(1);
            if (!(room.direction == 2) && !(IsVisited(new RoomEntrance(room.room, 2))))
                list.Add(2);
            if (!(room.direction == 3) && !(IsVisited(new RoomEntrance(room.room, 3))))
                list.Add(3);
            return list[ScreenManager.r.Next(list.Count)];
        }
        private void RandomOpen(RoomEntrance room, Dictionary<IntVector2, Room> maze)
        {
            for (int i = 0; i < 3; i++)
            {
                if (room.direction == i)
                    continue;
                if (!(room.room.link[room.direction, i]))
                {
                    if (ScreenManager.r.Next(100) < openRate)
                    {
                        Open(room, i);
                        Open(room.room, i, maze);
                    }
                }
            }
        }
        private Dictionary<IntVector2, Room> MazeGen(Dictionary<IntVector2, Room> maze, Stack<RoomEntrance> stack, RoomEntrance current)
        {
            if (stack.Count == 0)
                return maze;
            if (DeadEnd(current))
            {
                RandomOpen(current, maze);

                RoomEntrance temp = stack.Pop();
                return MazeGen(maze, stack, temp);
            }
            else
            {
                int temp = NextRoom(current);
                Room tempRoom = Side(current.room, temp, maze);
                Open(current, temp);
                Open(current.room, temp, maze);
                stack.Push(current);
                path.Add(current);
                stack.Push(new RoomEntrance(current.room, temp));
                path.Add(new RoomEntrance(current.room, temp));
                maze = MazeGen(maze, stack, new RoomEntrance(Side(current.room, temp, maze), RoomEntrance.Opposite(temp)));
                RandomOpen(current, maze);
                return maze;
            }
        }
        
        /// <summary>
        /// width*height의 maze를 만들어서 top left에 붙인다
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="left"></param>
        /// <param name="top"></param>
        /// <param name="direction"></param>
        private void GenerateMap(int width, int height, int left, int top, bool isBase)
        {
            Stack<RoomEntrance> stack = new Stack<RoomEntrance>();

            Dictionary<IntVector2, Room> generatedMaze = InitMaze(width, height);
            RoomEntrance start = new RoomEntrance(generatedMaze[new IntVector2(0, 0)], 0);
            OpenSingle(start.room, 0, generatedMaze);
            stack.Push(start);
            path = new List<RoomEntrance>();
            generatedMaze = MazeGen(generatedMaze, stack, start);
            if (isBase)
            {
                OpenAll(generatedMaze[new IntVector2(unitMazeWidth/2, unitMazeHeight/2)], generatedMaze);
            }
            generatedMaze = ConnectMaze(generatedMaze, width, height, left, top);
            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    if (isBase && i == unitMazeWidth / 2 && j == unitMazeHeight / 2)
                        generatedMaze[new IntVector2(i, j)].SelectPreset(screenManager, true);
                    else
                        generatedMaze[new IntVector2(i, j)].SelectPreset(screenManager);
                    roomList.Add(new IntVector2(left+i,top+j), generatedMaze[new IntVector2(i, j)]);
                }
            }
        }

        private Dictionary<IntVector2, Room> ConnectMaze(Dictionary<IntVector2, Room> maze, int width, int height, int left, int top)
        {
            if (roomList.ContainsKey(new IntVector2(left, top + height))) //down
            {
                for (int i = 0; i < width; i++)
                {
                    if (roomList[new IntVector2(left + i, top + height)].open[RoomEntrance.up])
                    {
                        Open(maze[new IntVector2(i, height - 1)], RoomEntrance.down, maze);
                    }
                }
            }
            else
            {
                for (int i = 0; i < width; i++)
                {
                    if (i == width / 2 || ScreenManager.r.Next(100) < edgeOpenRate)
                    {
                        OpenSingle(maze[new IntVector2(i, height - 1)], RoomEntrance.down, maze);
                    }
                }
            }

            if (roomList.ContainsKey(new IntVector2(left, top - 1))) //up
            {
                for (int i = 0; i < width; i++)
                {
                    if (roomList[new IntVector2(left + i, top - 1)].open[RoomEntrance.down])
                    {
                        Open(maze[new IntVector2(i, 0)], RoomEntrance.up, maze);
                    }
                }
            }
            else
            {
                for (int i = 0; i < width; i++)
                {
                    if (i == width / 2 || ScreenManager.r.Next(100) < edgeOpenRate)
                    {
                        OpenSingle(maze[new IntVector2(i, 0)], RoomEntrance.up, maze);
                    }
                }
            }

            if (roomList.ContainsKey(new IntVector2(left + width, top))) //right
            {
                for (int i = 0; i < width; i++)
                {
                    if (roomList[new IntVector2(left + width, top + i)].open[RoomEntrance.left])
                    {
                        Open(maze[new IntVector2(width - 1, i)], RoomEntrance.right, maze);
                    }
                }
            }
            else
            {
                for (int i = 0; i < height; i++)
                {
                    if (i == height / 2 || ScreenManager.r.Next(100) < edgeOpenRate)
                    {
                        OpenSingle(maze[new IntVector2(width - 1, i)], RoomEntrance.right, maze);
                    }
                }
            }

            if (roomList.ContainsKey(new IntVector2(left - 1, top))) //left
            {
                for (int i = 0; i < width; i++)
                {
                    if (roomList[new IntVector2(left - 1, top + i)].open[RoomEntrance.right])
                    {
                        Open(maze[new IntVector2(0, i)], RoomEntrance.left, maze);
                    }
                }
            }
            else
            {
                for (int i = 0; i < height; i++)
                {
                    if (i == height / 2 || ScreenManager.r.Next(100) < edgeOpenRate)
                    {
                        OpenSingle(maze[new IntVector2(0, i)], RoomEntrance.left, maze);
                    }
                }
            }

            
            
            return maze;
        }        
    }

    class RoomEntrance
    {
        public static int left = 0;
        public static int right = 1;
        public static int up = 2;
        public static int down = 3;
        public Room room;
        public int direction;

        public RoomEntrance(Room room, int direction)
        {
            this.room = room;
            this.direction = direction;
        }

        public static int Opposite(int direction)
        {
            if (direction == RoomEntrance.left)
                return RoomEntrance.right;
            else if (direction == RoomEntrance.right)
                return RoomEntrance.left;
            else if (direction == RoomEntrance.up)
                return RoomEntrance.down;
            else if (direction == RoomEntrance.down)
                return RoomEntrance.up;
            else
                return -1;
        }

        public static bool operator ==(RoomEntrance a, RoomEntrance b)
        {
            if (a.direction != b.direction)
                return false;
            if (System.Object.ReferenceEquals(a, b))
                return true;
            if (a.room.position == b.room.position)
                return true;
            return false;

            //return (System.Object.ReferenceEquals(a, b) && a.direction == b.direction);
        }

        public static bool operator !=(RoomEntrance a, RoomEntrance b)
        {
            return !(a == b);
        }

        public override bool Equals(object obj)
        {
            RoomEntrance b = obj as RoomEntrance;
            RoomEntrance a = this;
            return this == b;

        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
}
